/*
 *  Licensed to the Apache Software Foundation (ASF) under one or more
 *  contributor license agreements.  See the NOTICE file distributed with
 *  this work for additional information regarding copyright ownership.
 *  The ASF licenses this file to You under the Apache License, Version 2.0
 *  (the "License"); you may not use this file except in compliance with
 *  the License.  You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 *  Unless required by applicable law or agreed to in writing, software
 *  distributed under the License is distributed on an "AS IS" BASIS,
 *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 *  See the License for the specific language governing permissions and
 *  limitations under the License.
 */

package org.apache.harmony.auth.internal.kerberos.v5;

import java.io.IOException;
import java.math.BigInteger;
import java.net.DatagramPacket;
import java.net.DatagramSocket;
import java.net.InetAddress;
import java.util.ArrayList;
import java.util.Date;

import org.apache.harmony.auth.internal.nls.Messages;
import org.apache.harmony.security.asn1.ASN1Any;
import org.apache.harmony.security.asn1.ASN1Constants;
import org.apache.harmony.security.asn1.ASN1Explicit;
import org.apache.harmony.security.asn1.ASN1Integer;
import org.apache.harmony.security.asn1.ASN1Sequence;
import org.apache.harmony.security.asn1.ASN1SequenceOf;
import org.apache.harmony.security.asn1.ASN1StringType;
import org.apache.harmony.security.asn1.ASN1Type;

/**
 * @see http://www.ietf.org/rfc/rfc3961.txt
 * @see http://www.ietf.org/rfc/rfc4120.txt
 */
public class KDCRequest {

	/**
	 * Authentication Service request message type
	 */
	public static final int AS_REQ = 10;

	/**
	 * Ticket-Granting Service request message type
	 */
	public static final int TGS_REQ = 12;

	// type of a protocol message: AS_REQ or TGS_REQ
	private final int msgType;

	private final PrincipalName cname;

	private final String realm;

	private final PrincipalName sname;

	/**
	 * 
	 * <pre>
	 * KDC-REQ-BODY    ::= SEQUENCE {
	 *      kdc-options             [0] KDCOptions,
	 *      cname                   [1] PrincipalName OPTIONAL
	 *      -- Used only in AS-REQ --,
	 *      realm                   [2] Realm
	 *      -- Server's realm
	 *      -- Also client's in AS-REQ --,
	 *      sname                   [3] PrincipalName OPTIONAL,
	 *      from                    [4] KerberosTime OPTIONAL,
	 *      till                    [5] KerberosTime,
	 *      rtime                   [6] KerberosTime OPTIONAL,
	 *      nonce                   [7] UInt32,
	 *      etype                   [8] SEQUENCE OF Int32 -- EncryptionType
	 *      -- in preference order --,
	 *      addresses               [9] HostAddresses OPTIONAL,
	 *      enc-authorization-data  [10] EncryptedData OPTIONAL
	 *      -- AuthorizationData --,
	 *      additional-tickets      [11] SEQUENCE OF Ticket OPTIONAL
	 *      -- NOTE: not empty
	 *      }
	 * </pre>
	 */
	private static final ASN1Sequence KDC_REQ_BODY = new ASN1Sequence(
			new ASN1Type[] {
					new ASN1Explicit(0, ASN1Any.getInstance()), // TODO: ignored
					new ASN1Explicit(1, PrincipalName.ASN1),
					// TODO should we define Realm type?
					new ASN1Explicit(2, ASN1StringType.GENERALSTRING),
					new ASN1Explicit(3, PrincipalName.ASN1),
					new ASN1Explicit(4, ASN1Any.getInstance()), // TODO: ignored
					new ASN1Explicit(5, KerberosTime.getASN1()),
					new ASN1Explicit(6, ASN1Any.getInstance()), // TODO: ignored
					new ASN1Explicit(7, ASN1Integer.getInstance()),
					new ASN1Explicit(8, new ASN1SequenceOf(
							ASN1Integer.getInstance())),
					new ASN1Explicit(9, ASN1Any.getInstance()), // TODO: ignored
					new ASN1Explicit(10, ASN1Any.getInstance()), // TODO:
																	// ignored
					new ASN1Explicit(11, ASN1Any.getInstance()), // TODO:
																	// ignored

			}) {
		{
			setOptional(1); // cname
			setOptional(3); // sname
			setOptional(4); // from
			setOptional(6); // rtime
			setOptional(9); // addresses
			setOptional(10); // enc-authorization-data
			setOptional(11); // additional-tickets
		}

		@Override
		protected void getValues(Object object, Object[] values) {
			final KDCRequest request = (KDCRequest) object;

			// FIXME: hardcoded - no KDCOptions are set
			// note: number of bits should be >= 32
			// (see RFC 4120, 5.2.8. KerberosFlags)
			values[0] = new byte[] { (byte) 0x03, (byte) 0x05, (byte) 0x00,
					(byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, };

			values[1] = request.cname;
			values[2] = request.realm;
			values[3] = request.sname;

			// value[4] = from; //TODO

			// till: requested: "19700101000000Z" FIXME
			values[5] = new Date(0);

			// values[6] = rtime //TODO

			// nonce FIXME: hardcoded
			values[7] = BigInteger.valueOf(0).toByteArray();

			// etype FIXME
			final ArrayList<byte[]> list = new ArrayList<byte[]>();

			// see RFC 3961 (Section 8)
			list.add(BigInteger.valueOf(1).toByteArray());// des-cbc-crc
			list.add(BigInteger.valueOf(2).toByteArray());// des-cbc-md4
			list.add(BigInteger.valueOf(3).toByteArray());// des-cbc-md5
			values[8] = list;

			// value[9] = FIXME
			// value[10] = FIXME
			// value[11] = FIXME
		}
	};

	/**
	 * <pre>
	 * KDC-REQ         ::= SEQUENCE {
	 *      -- NOTE: first tag is [1], not [0]
	 *      pvno            [1] INTEGER (5) ,
	 *      msg-type        [2] INTEGER (10 -- AS -- | 12 -- TGS --),
	 *      padata          [3] SEQUENCE OF PA-DATA OPTIONAL
	 *      -- NOTE: not empty --,
	 *      req-body        [4] KDC-REQ-BODY
	 *      }
	 * </pre>
	 */
	static final ASN1Sequence KDC_REQ_ASN1 = new ASN1Sequence(new ASN1Type[] {
			// pvno [1] INTEGER (5)
			new ASN1Explicit(1, ASN1Integer.getInstance()),
			// msg-type [2] INTEGER
			new ASN1Explicit(2, ASN1Integer.getInstance()),
			// padata [3] SEQUENCE OF PA-DATA OPTIONAL
			new ASN1Explicit(3, new ASN1SequenceOf(ASN1Any.getInstance())),
			// req-body [4] KDC-REQ-BODY
			new ASN1Explicit(4, KDC_REQ_BODY), }) {
		{
			setOptional(2); // padata
		}

		@Override
		protected void getValues(Object object, Object[] values) {
			final KDCRequest request = (KDCRequest) object;

			values[0] = BigInteger.valueOf(5).toByteArray();
			values[1] = BigInteger.valueOf(request.msgType).toByteArray();
			// values[2] = //FIXME
			values[3] = request; // pass for further use
		}
	};

	static final ASN1Explicit AS_REQ_ASN1 = new ASN1Explicit(
			ASN1Constants.CLASS_APPLICATION, AS_REQ, KDC_REQ_ASN1);

	KDCRequest(int msgType, PrincipalName cname, String realm,
			PrincipalName sname) {
		super();
		this.msgType = msgType;
		this.cname = cname;
		this.realm = realm;
		this.sname = sname;
	}

	public DatagramSocket send(InetAddress address, int port)
			throws IOException {

		if (msgType != AS_REQ) {
			throw new RuntimeException(Messages.getString("auth.65"));
		}

		final byte[] enc = AS_REQ_ASN1.encode(this);

		final DatagramPacket req = new DatagramPacket(enc, enc.length, address,
				port);
		final DatagramSocket socket = new DatagramSocket();

		socket.send(req);

		return socket;
	}
}
